// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

//define avmm encoded events here
`define AVMM_IDLE    8'h00
`define AVMM_WR_REQ  8'h01
`define AVMM_RD_REQ  8'h02
`define AVMM_WR_CMPL 8'h04
`define AVMM_RD_CMPL 8'h08

module lvds_mm_sm_slave
    #(
        parameter  DATA_WIDTH    = 32,
        parameter  ADDR_WIDTH    = 16,
        parameter  TIMEOUT_PARAM = 25000
    ) 
(
    input                        clk,
    input                        reset,
    
    //memory interface connected to lvds_mm_slave
    input                        mem_write_req,
    input                        mem_read_req,    
    output reg                   mem_write_cmpl,   
    input       [ADDR_WIDTH-1:0] mem_address,
    input       [DATA_WIDTH-1:0] mem_writedata,
    output reg  [DATA_WIDTH-1:0] mem_readdata,
    output reg                   mem_readdatavalid,
    
    //Encoding AVMM events to ioc frame
    output reg  [7:0]           ioc_frame_o,
    input       [7:0]           ioc_frame_i,
    //tx/rx frame cnt
    input       [11:0]          tx_frm_offset,
    input       [11:0]          rx_frm_offset,
    
    //Sideband Signals connect to lvds_mm_slave module
    output                      avmm_timeout,
    output reg  [31:0]          timeout_cnt,      //Record how many timeout event has been happened
    output reg                  crc_check_pass,   //1'b1: Pass; 1'b0: Fail.
    output reg  [31:0]          crc_err_cnt,      //Record how many crc_error has been happened
    output reg                  unexpected_cmpl,
    output reg  [31:0]          unexpected_cmpl_cnt
);

    localparam IDLE          = 8'h00;
    localparam TX_CMD_HEADER = 8'h01;
    localparam TX_ADDR_B1    = 8'h02;
    localparam TX_ADDR_B0    = 8'h03;
    localparam TX_DATA_B3    = 8'h04;
    localparam TX_DATA_B2    = 8'h05;
    localparam TX_DATA_B1    = 8'h06;
    localparam TX_DATA_B0    = 8'h07;
    localparam CRC_SEND      = 8'h08;
    localparam CRC_CLR_WAIT  = 8'h09;
    localparam WAIT_CMPL     = 8'h0A;
    localparam RX_DATA_B3    = 8'h0B;
    localparam RX_DATA_B2    = 8'h0C;
    localparam RX_DATA_B1    = 8'h0D;
    localparam RX_DATA_B0    = 8'h0E;
    localparam CRC_CHECK     = 8'h0F;

reg  [ 7:0]  crc_data_in;     //bytes used to update CRC value
wire [15:0]  crc_out;         //CRC result          
    
reg  [ 7:0]  state;
reg          write_cmd_store; //store if current transaction is AVMM write   
reg          read_cmd_store;  //store if current transaction is AVMM read
reg          crc_gen_clr;
reg  [15:0]  address_hold;
reg  [31:0]  writedata_hold;
reg  [15:0]  crc_in;           //Initial and Previous crc value
reg          tx_crc_cal;       //Indicate current tx_byte should be used to update CRC
reg          rx_crc_cal;       //Indicate current rx_byte should be used to update CRC
reg  [15:0]  avmm_timer;
 

//Control State Machine
always @ (posedge clk) begin
    if (reset) begin
        ioc_frame_o         <= `AVMM_IDLE; //Send IDLE on the frame when no transaction.
        write_cmd_store     <= 1'b0;
        read_cmd_store      <= 1'b0;
        crc_gen_clr         <= 1'b1; // clear crc initial value
        address_hold        <= 16'b0;
        writedata_hold      <= 32'b0;
        mem_readdata        <= 32'b0;
        mem_readdatavalid   <= 1'b0;
        timeout_cnt         <= 16'b0;
        crc_err_cnt         <= 16'b0;
        tx_crc_cal          <= 1'b0;
        rx_crc_cal          <= 1'b0;
        crc_check_pass      <= 1'b1;
        unexpected_cmpl     <= 1'b0;
        unexpected_cmpl_cnt <= 16'b0;
        mem_write_cmpl      <= 1'b0;
        state               <= IDLE;
    end else begin
        case (state)
            IDLE: begin
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o       <= `AVMM_IDLE;
                    crc_gen_clr       <= 1'b1;
                    tx_crc_cal        <= 1'b0;
                    rx_crc_cal        <= 1'b0;
                end
                write_cmd_store   <= 1'b0;
                read_cmd_store    <= 1'b0;
                address_hold      <= mem_address;   //hold address and writedata value in idle state.
                writedata_hold    <= mem_writedata;
                mem_readdata      <= 32'b0;
                mem_readdatavalid <= 1'b0;  
                crc_check_pass    <= 1'b1;
                unexpected_cmpl   <= 1'b0;
                mem_write_cmpl    <= 1'b0;
                
                if ( mem_write_req || mem_read_req ) begin
                    write_cmd_store <= mem_write_req;
                    read_cmd_store  <= mem_read_req;
                    state           <= TX_CMD_HEADER;
                end                
            end
            
            TX_CMD_HEADER: begin //send out encoded cmd header in this stage
                crc_gen_clr <= 1'b0; //CMD_HEADER should be used to update crc value
                tx_crc_cal  <= 1'b1;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= write_cmd_store ? `AVMM_WR_REQ : `AVMM_RD_REQ;
                    state       <=  TX_ADDR_B1;
                end 
            end
            
            TX_ADDR_B1: begin //Send out address, 2 bytes, MSB First
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= address_hold[15:8];
                    state       <=  TX_ADDR_B0;
                end
            end
            
            TX_ADDR_B0: begin //Send out address, 2 bytes, MSB First
                tx_crc_cal  <= 1'b1;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= address_hold[ 7:0]; 
                    
                    if (read_cmd_store) begin //read transaction, go directly to CRC_SEND after addr 
                        state <= CRC_SEND;
                    end else begin
                        state <= TX_DATA_B3; //write transaction, send writedata after addr
                    end
                end
            end            
            
            TX_DATA_B3: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= writedata_hold[31:24];
                    state       <= TX_DATA_B2;
                end
            end
            
            TX_DATA_B2: begin
                tx_crc_cal  <= 1'b1;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= writedata_hold[23:16];
                    state       <= TX_DATA_B1;
                end
            end            
            
            TX_DATA_B1: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= writedata_hold[15: 8];
                    state       <= TX_DATA_B0;
                end
            end           
            
            TX_DATA_B0: begin
                tx_crc_cal  <= 1'b1;
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= writedata_hold[ 7: 0];
                    state       <= CRC_SEND;
                end
            end
            
            CRC_SEND: begin
                tx_crc_cal  <= 1'b0;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= crc_out[7:0];  
                    state       <= WAIT_CMPL;
                end
            end
            
            WAIT_CMPL: begin //Wait for compltion from remote end. clear crc in this state
                crc_gen_clr <= 1'b1;
                
                if (tx_frm_offset%12 == 6) begin
                    ioc_frame_o <= `AVMM_IDLE;
                end
                
                if (rx_frm_offset%12 == 0) begin
                    if (avmm_timeout) begin
                        timeout_cnt <= timeout_cnt + 1'b1; //Record how many timeout has been happeded
                        state       <= IDLE;
                    end else if ( (ioc_frame_i == `AVMM_RD_CMPL) && read_cmd_store ) begin //begin to receive rx_data
                        crc_gen_clr <= 1'b0;
                        rx_crc_cal  <= 1'b1;
                        state       <= RX_DATA_B3;
                    end else if ( (ioc_frame_i == `AVMM_WR_CMPL) && write_cmd_store ) begin//begin to compare cec for write_cmpl code
                        crc_gen_clr <= 1'b0;
                        tx_crc_cal  <= 1'b1;
                        state       <= CRC_CHECK;
                    end else if ( ioc_frame_i !=`AVMM_IDLE ) begin
                        unexpected_cmpl     <= 1'b1;
                        unexpected_cmpl_cnt <= unexpected_cmpl_cnt + 1'b1;
                        state               <= IDLE;
                    end else begin
                        state               <= WAIT_CMPL;
                    end 
                end
            end
            
            RX_DATA_B3: begin
                rx_crc_cal          <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    mem_readdata[31:24] <= ioc_frame_i;
                    state               <= RX_DATA_B2;
                end
            end
            
            RX_DATA_B2: begin
                rx_crc_cal          <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    mem_readdata[23:16] <= ioc_frame_i;
                    state               <= RX_DATA_B1;
                end
            end            
            
            RX_DATA_B1: begin
                rx_crc_cal          <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    mem_readdata[15: 8] <= ioc_frame_i;
                    state               <= RX_DATA_B0;
                end
            end            
            
            RX_DATA_B0: begin
                rx_crc_cal          <= 1'b1;
                if (rx_frm_offset%12 == 0) begin
                    mem_readdata[ 7: 0] <= ioc_frame_i;
                    state               <= CRC_CHECK;
                end
            end            
            
            CRC_CHECK: begin
                if (rx_frm_offset%12 == 0) begin
                    rx_crc_cal       <= 1'b0;
                    tx_crc_cal       <= 1'b0;
                    crc_check_pass   <= ( ioc_frame_i == crc_out[7:0] );
                    
                    if ( ioc_frame_i == crc_out[7:0] ) begin //CRC check pass
                        if (write_cmd_store) begin
                            mem_write_cmpl    <= 1'b1;       //assert mem_write_cmpl when WR_CMPL recrived from remote end succesfully.
                        end else begin
                            mem_readdatavalid <= 1'b1;       //assert mem_readdatavalid when RD_CMPL & DATA recrived from remote end succesfully.
                        end
                        state <= IDLE;
                    end else begin
                        crc_err_cnt <= crc_err_cnt + 1'b1;
                        state       <= IDLE;
                    end
                end
            end
            
            default: begin
                address_hold    <= 16'b0;
                writedata_hold  <= 32'b0;
                write_cmd_store <= 1'b0;
                read_cmd_store  <= 1'b0;
                crc_gen_clr     <= 1'b1;
                tx_crc_cal      <= 1'b0;
                rx_crc_cal      <= 1'b0;
                crc_check_pass  <= 1'b1;
                unexpected_cmpl <= 1'b0;
                state           <= IDLE;
            end
        endcase
    end //non reset    
end

//Watchdog Timer
always @ (posedge clk) begin
    if (reset) begin
        avmm_timer <= 16'h0;
    end else if ( state == IDLE ) begin
        avmm_timer <= 16'h0;
    end else if ( state == WAIT_CMPL ) begin
        avmm_timer <= avmm_timer + 1'b1;
    end
end

assign avmm_timeout = avmm_timer == TIMEOUT_PARAM;

//CRC Generator_Checker
crc16_dat8 crc_gen_check
(
    .crc_in   ( crc_in      ),
    .dat_in   ( crc_data_in ), 
    .crc_out  ( crc_out     )
);

always @ (posedge clk)
  if (reset)
    crc_in <= 16'h0;
  else if (crc_gen_clr)
    crc_in <= 16'h0;
  else if ( (tx_crc_cal&(tx_frm_offset%12 == 7)) | (rx_crc_cal&(rx_frm_offset%12 == 1)) )
    crc_in <= crc_out;

always @ (posedge clk) begin
    if (reset) begin
        crc_data_in <= 8'h0;
    end else if (crc_gen_clr) begin
        crc_data_in <= 'h0;    
    end else if ( rx_crc_cal&(rx_frm_offset%12 == 1) ) begin
        crc_data_in <= ioc_frame_i;
    end else if ( tx_crc_cal&(tx_frm_offset%12 == 7) ) begin
        crc_data_in <= ioc_frame_o;
    end else begin
        crc_data_in <= crc_data_in;
    end
end

endmodule    